package me.sealer.ssm.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import me.sealer.ssm.constant.ErrorCodeConstant;
import me.sealer.ssm.constant.GiteeConstant;
import me.sealer.ssm.constant.GithubConstant;
import me.sealer.ssm.constant.TokenConstant;
import me.sealer.ssm.constant.UserOrigin;
import me.sealer.ssm.controller.dto.AccountLoginRequest;
import me.sealer.ssm.controller.dto.CommonResponse;
import me.sealer.ssm.controller.dto.GiteeLoginRequest;
import me.sealer.ssm.controller.dto.GithubLoginRequest;
import me.sealer.ssm.controller.dto.LoginResponse;
import me.sealer.ssm.controller.dto.QuickLoginRequest;
import me.sealer.ssm.controller.dto.RegisterRequest;
import me.sealer.ssm.exception.SsmException;
import me.sealer.ssm.model.Menu;
import me.sealer.ssm.model.User;
import me.sealer.ssm.service.UserMenuService;
import me.sealer.ssm.service.UserService;
import me.sealer.ssm.token.TokenManager;
import me.sealer.ssm.utils.StringUtil;
import me.sealer.ssm.utils.UsernameUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Assert;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author sealer
 * @date 2016/09/27
 */
@Slf4j
@RestController
@Api(tags = "用户注册、登录和登出相关api")
public class SsmController {
    private static final String MY_GITHUB_CLIENT_ID = "8ffda36a06ccb0fe74a3";
    private static final String MY_GITHUB_CLIENT_SECRET = "853188b07a757e0c99205138bf56e94d44c41d74";

    private static final String MY_GITEE_CLIENT_ID = "96999b21ee76311576c8bf0e1441035d4b062c74b2158cdaaffa08ae8385189b";
    private static final String MY_GITEE_CLIENT_SECRET = "e4446ee18d1c0f71313f1b7d3453dc7bf07855f1a6e68d58874ad7c3a618d043";

    private final UserService userService;

    private final UserMenuService userMenuService;

    @Resource(name = "imageTokenManager")
    private TokenManager imageTokenManager;

    @Resource(name = "phoneTokenManager")
    private TokenManager phoneTokenManager;

    @Resource(name = "resubmitTokenManager")
    private TokenManager resubmitTokenManager;

    @Resource(name = "restTemplate")
    private RestTemplate restTemplate;

    public SsmController(UserService userService, UserMenuService userMenuService) {
        this.userService = userService;
        this.userMenuService = userMenuService;
    }

    @ApiOperation(value = "账号方式登录")
    @PostMapping(value = "/login", produces = MediaType.APPLICATION_JSON_VALUE)
    public CommonResponse<LoginResponse> login(@RequestBody AccountLoginRequest accountLoginRequest, HttpServletRequest httpServletRequest) {
        String username = accountLoginRequest.getUsername();
        LoginResponse loginResponse;

        // 纯数字，改为手机号方式登录
        if (UsernameUtil.isPhoneNumber(username)) {
            loginResponse = phoneLoginInner(accountLoginRequest, httpServletRequest);

            // 包含@符号， 改为email方式登录
        } else if (!UsernameUtil.notContainsAt(username)) {
            loginResponse = emailLoginInner(accountLoginRequest, httpServletRequest);

            // 正常用户名方式登录
        } else {
            loginResponse = accountLoginInner(accountLoginRequest, httpServletRequest);
        }

        return new CommonResponse<>(loginResponse);
    }

    @ApiOperation(value = "手机验证码快捷登录")
    @PostMapping(value = "/quickLogin", produces = MediaType.APPLICATION_JSON_VALUE)
    public CommonResponse<LoginResponse> quickLogin(@RequestBody QuickLoginRequest quickLoginRequest, HttpServletRequest httpServletRequest) {
        // 图形验证码
        int verifyResultCode = imageTokenManager.verifyToken(httpServletRequest.getSession(), quickLoginRequest.getImageTokenName());
        if (TokenConstant.TOKEN_SUCCESS != verifyResultCode) {
            throw new SsmException(ErrorCodeConstant.TOKEN_IMAGE_TOKEN_ERROR);
        }

        // 防重复提交码
        verifyResultCode = resubmitTokenManager.verifyToken(httpServletRequest.getSession(), quickLoginRequest.getResubmitTokenName());
        if (TokenConstant.TOKEN_SUCCESS != verifyResultCode) {
            throw new SsmException(ErrorCodeConstant.TOKEN_RESUBMIT_TOKEN_ERROR);
        }

        // 手机验证码
        verifyResultCode = phoneTokenManager.verifyToken(httpServletRequest.getSession(), quickLoginRequest.getPhoneTokenName());
        if (TokenConstant.TOKEN_SUCCESS != verifyResultCode) {
            throw new SsmException(ErrorCodeConstant.TOKEN_PHONE_TOKEN_ERROR);
        }

        String phoneNumber = quickLoginRequest.getPhoneNumber();

        // 快速登录
        userService.quickLogin(phoneNumber);

        Subject subject = SecurityUtils.getSubject();
        List<Menu> menus = userMenuService.getMenusByUsername(subject.getPrincipal().toString());
        return new CommonResponse<>(LoginResponse.builder()
                .username(subject.getPrincipal().toString())
                .menus(menus)
                .build());
    }

    @ApiOperation(value = "第三方授权登录之github")
    @PostMapping(value = "/callback/github", produces = MediaType.APPLICATION_JSON_VALUE)
    public CommonResponse<LoginResponse> githubLogin(@RequestBody GithubLoginRequest githubLoginRequest, HttpServletRequest httpServletRequest) {
        // TODO 此处如何页面跳转
        LoginResponse loginResponse = githubLoginInner(githubLoginRequest, httpServletRequest);
        return new CommonResponse<>(loginResponse);
    }

    @ApiOperation(value = "第三方授权登录之gitee")
    @PostMapping(value = "/callback/gitee", produces = MediaType.APPLICATION_JSON_VALUE)
    public CommonResponse<LoginResponse> giteeLogin(@RequestBody GiteeLoginRequest giteeLoginRequest, HttpServletRequest httpServletRequest) {
        // TODO 此处如何页面跳转
        LoginResponse loginResponse = giteeLoginInner(giteeLoginRequest, httpServletRequest);
        return new CommonResponse<>(loginResponse);
    }

    @ApiOperation(value = "用户注册")
    @PostMapping(value = "/register", produces = MediaType.APPLICATION_JSON_VALUE)
    public CommonResponse register(@Valid @RequestBody RegisterRequest registerRequest, HttpServletRequest request) {

        String username = registerRequest.getUsername();

        // 注册用户用户名不允许为纯数字
        boolean isPureNumber = UsernameUtil.isNumber(username);
        if (isPureNumber) {
            throw new SsmException(ErrorCodeConstant.USER_USERNAME_CANNOT_BE_PURE_NUMBER);
        }

        // 注册用户用户名不允许包含@符号
        boolean notContainsAt = UsernameUtil.notContainsAt(username);
        if (!notContainsAt) {
            throw new SsmException(ErrorCodeConstant.USER_USERNAME_CANNOT_CONTAINS_AT);
        }

        String password = registerRequest.getPassword();
        String confirmPassword = registerRequest.getConfirmPassword();
        // 判断二次输入的新确认密码是否与新设置的密码相同
        boolean passNewConfirmEquals = StringUtil.equals(password, confirmPassword);
        if (!passNewConfirmEquals) {
            throw new SsmException(ErrorCodeConstant.USER_PASSWORD_CONFIRM_WRONG);
        }

        User user = User.builder()
                .username(username)
                .password(password)
                .birthday(registerRequest.getBirthday())
                .address(registerRequest.getAddress())
                .sex(registerRequest.getSex())
                .build();

        userService.createUser(user);
        return new CommonResponse<>();
    }

    @ApiOperation(value = "用户登出")
    @GetMapping(value = "/logout", produces = MediaType.APPLICATION_JSON_VALUE)
    public CommonResponse logout() {
        Subject subject = SecurityUtils.getSubject();
        // 退出
        subject.logout();
        return new CommonResponse();
    }

    /**
     * github login inner
     *
     * @param loginRequest       login request
     * @param httpServletRequest httpServletRequest
     * @return LoginResponse
     */
    private LoginResponse accountLoginInner(AccountLoginRequest loginRequest, HttpServletRequest httpServletRequest) {
        Boolean rememberMe = loginRequest.getRememberMe();
        String password = loginRequest.getPassword();
        String username = loginRequest.getUsername();

        // 校验图形验证码
        String tokenId = loginRequest.getImageTokenName();
        int verifyResultCode = imageTokenManager.verifyToken(httpServletRequest.getSession(), tokenId);
        if (TokenConstant.TOKEN_SUCCESS != verifyResultCode) {
            throw new SsmException(ErrorCodeConstant.TOKEN_IMAGE_TOKEN_ERROR);
        }

        userService.login(username, password, rememberMe);

        Subject subject = SecurityUtils.getSubject();
        List<Menu> menus = userMenuService.getMenusByUsername(subject.getPrincipal().toString());
        return LoginResponse.builder()
                .username(subject.getPrincipal().toString())
                .menus(menus)
                .build();
    }

    private LoginResponse emailLoginInner(AccountLoginRequest accountLoginRequest, HttpServletRequest httpServletRequest) {
        String email = accountLoginRequest.getUsername();
        User user = userService.queryUserByEmail(email);
        if (user == null) {
            throw new SsmException(ErrorCodeConstant.USER_USER_NOT_EXISTS, new Object[]{email});
        }
        accountLoginRequest.setUsername(user.getUsername());
        return accountLoginInner(accountLoginRequest, httpServletRequest);
    }

    private LoginResponse phoneLoginInner(AccountLoginRequest accountLoginRequest, HttpServletRequest httpServletRequest) {
        String phone = accountLoginRequest.getUsername();
        User user = userService.queryUserByPhoneNumber(phone);
        if (user == null) {
            throw new SsmException(ErrorCodeConstant.USER_USER_NOT_EXISTS, new Object[]{phone});
        }
        accountLoginRequest.setUsername(user.getUsername());
        return accountLoginInner(accountLoginRequest, httpServletRequest);
    }

    /**
     * github login inner
     *
     * @param githubLoginRequest login request
     * @param httpServletRequest httpServletRequest
     * @return LoginResponse
     */
    private LoginResponse githubLoginInner(GithubLoginRequest githubLoginRequest, HttpServletRequest httpServletRequest) {
        String code = githubLoginRequest.getCode();

        Map<String, String> githubAccessTokenRequestMap = new HashMap<>(8);
        githubAccessTokenRequestMap.put(GithubConstant.CLIENT_ID, MY_GITHUB_CLIENT_ID);
        githubAccessTokenRequestMap.put(GithubConstant.CLIENT_SECRET, MY_GITHUB_CLIENT_SECRET);
        githubAccessTokenRequestMap.put(GithubConstant.CODE, code);

        ResponseEntity<Map> githubAccessTokenResponseEntity = restTemplate.postForEntity(GithubConstant.GITHUB_ACCESS_TOKEN_URL, githubAccessTokenRequestMap, Map.class);
        Map githubAccessTokenResponseMap = githubAccessTokenResponseEntity.getBody();

        Assert.isTrue(githubAccessTokenResponseMap != null, "通信异常");
        String accessToken = (String) githubAccessTokenResponseMap.get(GithubConstant.ACCESS_TOKEN);

        String githubUserRequestUrl = GithubConstant.GITHUB_USER_URL
                + "?"
                + GithubConstant.ACCESS_TOKEN
                + "="
                + accessToken;

        ResponseEntity<Map> githubUserResponseEntity = restTemplate.getForEntity(githubUserRequestUrl, Map.class);
        Map userResponseEntityMap = githubUserResponseEntity.getBody();

        Assert.isTrue(userResponseEntityMap != null, "通信异常");

        String username = (String) userResponseEntityMap.get(GithubConstant.LOGIN);
        String userAvatarUrl = (String) userResponseEntityMap.get(GithubConstant.AVATAR_URL);

        userService.oauth2Login(username, UserOrigin.GITHUB, userAvatarUrl);

        Subject subject = SecurityUtils.getSubject();
        List<Menu> menus = userMenuService.getMenusByUsername(subject.getPrincipal().toString());
        return LoginResponse.builder()
                .username(subject.getPrincipal().toString())
                .menus(menus)
                .build();
    }

    /**
     * gitee login inner
     *
     * @param giteeLoginRequest  login request
     * @param httpServletRequest httpServletRequest
     * @return loginResponse
     */
    private LoginResponse giteeLoginInner(GiteeLoginRequest giteeLoginRequest, HttpServletRequest httpServletRequest) {
        String code = giteeLoginRequest.getCode();

        MultiValueMap<String, String> giteeAccessTokenRequestMap = new LinkedMultiValueMap<>(8);
        giteeAccessTokenRequestMap.add(GiteeConstant.CLIENT_ID, MY_GITEE_CLIENT_ID);
        giteeAccessTokenRequestMap.add(GiteeConstant.CLIENT_SECRET, MY_GITEE_CLIENT_SECRET);
        giteeAccessTokenRequestMap.add(GiteeConstant.CODE, code);
        // TODO sealer 此处需要参数化
        giteeAccessTokenRequestMap.add(GiteeConstant.REDIRECT_URI, "http://localhost:8888/ssm/callback/gitee");
        // oauth2授权码模式
        giteeAccessTokenRequestMap.add(GiteeConstant.GRANT_TYPE, "authorization_code");

        //headers
        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36");

        //HttpEntity
        HttpEntity<MultiValueMap> accessTokenRequestEntity = new HttpEntity<>(giteeAccessTokenRequestMap, requestHeaders);

        ResponseEntity<Map> giteeAccessTokenResponseEntity = restTemplate.postForEntity(GiteeConstant.GITEE_ACCESS_TOKEN_URL, accessTokenRequestEntity, Map.class);
        Map giteeAccessTokenResponseMap = giteeAccessTokenResponseEntity.getBody();

        Assert.isTrue(giteeAccessTokenResponseMap != null, "通信异常");

        String accessToken = (String) giteeAccessTokenResponseMap.get(GiteeConstant.ACCESS_TOKEN);

        String giteeUserRequestUrl = GiteeConstant.GITEE_USER_URL
                + "?"
                + GiteeConstant.ACCESS_TOKEN
                + "="
                + accessToken;

        //HttpEntity
        HttpEntity<MultiValueMap> userRequestEntity = new HttpEntity<>(null, requestHeaders);

        ResponseEntity<Map> giteeUserResponseEntity = restTemplate.exchange(giteeUserRequestUrl, HttpMethod.GET, userRequestEntity, Map.class);
        Map userResponseEntityMap = giteeUserResponseEntity.getBody();

        Assert.isTrue(userResponseEntityMap != null, "通信异常");

        String username = (String) userResponseEntityMap.get(GiteeConstant.LOGIN);
        String userAvatarUrl = (String) userResponseEntityMap.get(GiteeConstant.AVATAR_URL);

        userService.oauth2Login(username, UserOrigin.GITEE, userAvatarUrl);

        Subject subject = SecurityUtils.getSubject();
        List<Menu> menus = userMenuService.getMenusByUsername(subject.getPrincipal().toString());
        return LoginResponse.builder()
                .username(subject.getPrincipal().toString())
                .menus(menus)
                .build();
    }
}